package com.soup.lab.basic.proxy;

import org.junit.jupiter.api.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Objects;

public class JDKProxyDemoTestCase {

    /*
        定义了IA、IB、IC三个无关的接口，并通过 A、B、C分别实现。
        JDK动态代理可以轻松的生成实现任意接口组合的代理类。
     */
    @Test
    public void test() {
        IA a = new A();
        IB b = new B();
        IC c = new C();

        Object ab = Proxy.newProxyInstance(getClass().getClassLoader(),
                new Class[]{IA.class, IB.class}, new CompositeInvocationHandler(a, b));
        ((IA)ab).a();
        ((IB)ab).b();

        Object abc = Proxy.newProxyInstance(getClass().getClassLoader(),
                new Class[]{IA.class, IB.class, IC.class}, new CompositeInvocationHandler(a, b, c));
        ((IA)abc).a();
        ((IB)abc).b();
        ((IC)abc).c();
    }

    interface IA {
        void a();
    }
    interface IB {
        void b();
    }
    interface IC {
        void c();
    }

    static class A implements IA {
        @Override
        public void a() {
            System.out.println("I'm an A!");
        }
    }
    static class B implements IB {
        @Override
        public void b() {
            System.out.println("I'm a B!");
        }
    }
    static class C implements IC {
        @Override
        public void c() {
            System.out.println("I'm a C!");
        }
    }

    static class CompositeInvocationHandler implements InvocationHandler {

        private final Object[] targets;

        public CompositeInvocationHandler(Object... targets) {
            if(targets == null || targets.length == 0) {
                throw new IllegalArgumentException("The param 'targets' must not be null or empty.");
            }
            for (Object target : targets) {
                if(target == null) {
                    throw new IllegalArgumentException("The elements of 'targets' array must not be null.");
                }
            }
            this.targets = targets;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Class<?> methodDeclaringClass = method.getDeclaringClass();
            if(methodDeclaringClass == Object.class) {
                if("equals".equals(method.getName())) {
                    if(args[0] == null || !(args[0] instanceof Proxy)) {
                        return false;
                    }
                    return Objects.equals(Proxy.getInvocationHandler(proxy),
                            Proxy.getInvocationHandler(args[0]));
                } else if("hashCode".equals(method.getName())) {
                    return hashCode();
                } else if("toString".equals(method.getName())) {
                    return toString();
                }
            }
            for (Object target : targets) {
                if(methodDeclaringClass.isAssignableFrom(target.getClass())) {
                    return method.invoke(target, args);
                }
            }
            throw new UnsupportedOperationException();
        }

        @Override
        public int hashCode() {
            return Objects.hash(targets);
        }

        @Override
        public boolean equals(Object obj) {
            if(obj == null) {
                return false;
            }
            if(this == obj) {
                return true;
            }
            if(obj.getClass() != CompositeInvocationHandler.class) {
                return false;
            }
            return Objects.deepEquals(targets, ((CompositeInvocationHandler) obj).targets);
        }

        @Override
        public String toString() {
            return "CompositeInvocationHandler{" +
                    "targets=" + Arrays.toString(targets) +
                    '}';
        }
    }

}
